Skip to content

Exercises

Toggle Component

Let's migrate a cute on/off <Toggle> component to use Framer Motion!

Below, you'll find a fully-functional Toggle component, but it uses CSS transitions. Your mission is to convert it to use Framer Motion.

Acceptance Criteria:

  • The animation should use Framer Motion rather than a CSS transition.
  • The animation should not happen on mount. It should only animate when the value changes.
  • A spring transition should be used. In terms of the specific transition settings, feel free to tinker with stiffness and damping until you're happy with the results!

Code Playground

import React from 'react';
import { motion } from 'framer-motion';

import styles from './Toggle.module.css';

function Toggle({
value,
onChange,
...delegated
}) {
return (
<button
type="button"
role="switch"
aria-checked={value}
className={styles.wrapper}
onClick={() => onChange(!value)}
{...delegated}
>
<span
className={styles.ball}
style={{
transition: 'transform 300ms',
transform: `translateX(${
value ? '100%' : '0%'
})`,
}}
/>
</button>
);
}

export default Toggle;

Solution:

Springy Toasty

Back in Module 3, we created a Mortal-Kombat-inspired “Toasty!” animation:

We used a fairly-typical CSS transition, and the effect is alright, but I think we can do better.

Using Framer Motion, let's update this animation to use a really smooth spring:

Acceptance Criteria:

  • The Toasty component should be updated to use Framer Motion.
  • A custom transition should be set, manually specifying stiffness and damping. If you'd like, you can try to match the animation in the above GIF, but you can also experiment and come up with something different!
  • Stretch goal: Tweak the values for stiffness/damping depending on whether the character is entering or exiting. The exit animation should be much quicker:

Code Playground

import React from 'react';
import { motion } from 'framer-motion';

import styles from './Toasty.module.css';

function Toasty() {
const [isShown, setIsShown] = React.useState(false);

const wrapperRef = React.useRef();

React.useEffect(() => {
const observer = new IntersectionObserver((entries) => {
const [entry] = entries;

setIsShown(entry.isIntersecting);
});

observer.observe(wrapperRef.current);
}, []);

const translateX = isShown ? '0%' : '100%';

return (
<div ref={wrapperRef} className={styles.wrapper}>
<div
className={styles.character}
style={{
transition: 'transform 200ms',
transform: `translateX(${translateX})`,
}}
>
👻
</div>
</div>
);
}

export default Toasty;

Solution:

Note: This is one of those solution videos where we learn some new stuff. Specifically, we dig deeper into how spring animations really work, and learn about the restDelta property.

Also, this video doesn't include the “stretch goal”, because I only thought of it after I had finished recording 😅. You can see my approach in the solution code below: